#include "Texture.h"
#include "TextureMgr.h"
#include "D3D11Client.h"

//==========================================================================
//            Texture or Surface creation/loading
//==========================================================================

Texture::Texture( TextureMgr * textureMgr, DWORD w, DWORD h ) 
    :m_textureMgr(textureMgr)
{
//    Creates GDI compatible surface without initial data.
    D3D11_TEXTURE2D_DESC Desc;
    ZeroMemory( &Desc, sizeof( Desc ) );
    bTex = false;

    bSRV = bRTV = false;
    SRV = NULL;
    RTV = NULL;
    file_name = NULL;
    RefCounter = 1;

    has_alpha = true;
    bColorKey = false;
    ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;

    Desc.Width = width = w;
    Desc.Height = height = h;
    Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
    Desc.MipLevels = 1;
    Desc.Usage = D3D11_USAGE_DEFAULT;
    Desc.SampleDesc.Count = 1;
    Desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;            //required for GDI compatibility
    Desc.ArraySize = 1;
    Desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;//required for GDI compatibility
    Desc.CPUAccessFlags = 0;

    HR( gd3dDevice->CreateTexture2D( &Desc, NULL, &Tex ) );
    //HR( Tex->QueryInterface( __uuidof(IDXGISurface1), reinterpret_cast<void**>(&Surf) ) );
#if _DEBUG
    {
        char buffer[200];
        buffer[199] = '\0';
        sprintf(buffer, "[Texture::Texture]%dx%d", w, h);
        if (Tex)
            Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
    }
#endif
}

Texture::Texture( TextureMgr * textureMgr, DWORD w, DWORD h, void *data ) 
    :m_textureMgr(textureMgr)
{
//    Creates GDI compatible surface from R8G8B8A8 bitmap.
    D3D11_TEXTURE2D_DESC Desc;
    ZeroMemory( &Desc, sizeof( Desc ) );
    Desc.Width = width = w;
    Desc.Height = height = h;
    Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
    Desc.MipLevels = 1;
    Desc.CPUAccessFlags = 0;
    Desc.Usage = D3D11_USAGE_DEFAULT;
    Desc.SampleDesc.Count = 1;
    Desc.SampleDesc.Quality = 0;
    Desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    Desc.ArraySize = 1;
    Desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;

    D3D11_SUBRESOURCE_DATA SData;
    ZeroMemory( &SData, sizeof( SData ) );
    SData.pSysMem = data;
    SData.SysMemPitch = width*4;

    HR( gd3dDevice->CreateTexture2D( &Desc, &SData, &Tex ) );
    //HR( Tex->QueryInterface( __uuidof(IDXGISurface1), reinterpret_cast<void**>(&Surf) ) );
#if _DEBUG
    {
        char buffer[200];
        buffer[199] = '\0';
        sprintf(buffer, "[Texture::Texture, data]%dx%d", w, h);
        if (Tex)
            Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
    }
#endif
    SRV = NULL;
    RTV = NULL;
    file_name = NULL;
    has_alpha = true;
    bTex = bSRV = bRTV = false;
    RefCounter = 1;
    bColorKey = false;
    ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
}

Texture::Texture( TextureMgr * textureMgr, DWORD w, DWORD h, DXGI_FORMAT Format, const char *fname, bool alpha ) 
    :m_textureMgr(textureMgr)
{
//used for creation of normal map texture from the red channel of bump map.
    D3D11_TEXTURE2D_DESC Desc;
    ZeroMemory( &Desc, sizeof( Desc ) );
    Desc.Width = width = w;
    Desc.Height = height = h;
    Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	Desc.MipLevels = textureMgr->getGraphicsClient()->getConfig()->TextureMipMapCount;
    Desc.CPUAccessFlags = 0;
    Desc.Usage = D3D11_USAGE_DEFAULT;
    Desc.SampleDesc.Count = 1;
    Desc.SampleDesc.Quality = 0;
    Desc.Format = Format;
    Desc.ArraySize = 1;
    Desc.MiscFlags = 0;

    HR( gd3dDevice->CreateTexture2D( &Desc, NULL, &Tex ) );

    D3D11_SHADER_RESOURCE_VIEW_DESC SDesc;
    ZeroMemory( &SDesc, sizeof( SDesc ));
    SDesc.Format = Format;
    SDesc.Texture2D.MipLevels = 1;
    SDesc.Texture2D.MostDetailedMip = 0;
    SDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

    HR( gd3dDevice->CreateShaderResourceView( Tex, &SDesc, &SRV ) );
    bTex = bSRV = true;

    Surf = NULL;
    RTV = NULL;

    UINT len = strlen( fname );
    if( len ) {
        file_name = new char [len+1];
        strcpy( file_name, fname );
    }

    has_alpha = alpha;
    bRTV = false;
    RefCounter = 1;
    bColorKey = false;
    ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
#if _DEBUG
    {
        char buffer[200];
        buffer[199] = '\0';
        sprintf(buffer, "[Texture::Texture2]%s", fname);
        if (Tex)
            Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
        if (SRV)
            SRV->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
    }
#endif
}

Texture::Texture( TextureMgr * textureMgr, const char *fname, const char *nameWithPath ) 
    : m_textureMgr(textureMgr)
{
//    Loads a texture from file without format modification (if possible).
//    char path[256];
    D3DX11_IMAGE_INFO Info;
    D3DX11_IMAGE_LOAD_INFO LoadInfo;
    HRESULT hr;

    //init:
    Tex = NULL;            //file not found.
    SRV = NULL;
    Surf = NULL;
    RTV = NULL;

    bTex = true;
    has_alpha = bSRV = bRTV = bColorKey = false;
    ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
    RefCounter = 1;

    ZeroMemory( &Info, sizeof(Info) );
    ZeroMemory( &LoadInfo, sizeof(LoadInfo) );

    //load:
    if (nameWithPath == nullptr)
    {
        nameWithPath = fname;
    }
    file_name = NULL;
    file_name = new char [strlen(fname)+1];
    strcpy( file_name, fname );

    FILE *file;
    BYTE *data;

    if( !(file = fopen( nameWithPath, "rb" )) )
        return;
    fseek( file, 0, SEEK_END );
    DWORD file_size = ftell( file );
    fseek( file, 0, SEEK_SET );

    data = new BYTE [file_size];
    fread( data, 1, file_size, file );
    fclose( file );

    if( *((DWORD*)data) != MAKEFOURCC( 'D', 'D', 'S', ' ' ) ) {
        delete [ ] data;    //not .dds
        return;
    }

//    DDSURFACEDESC2 *ddesc = (DDSURFACEDESC2*)(data+4);
//    has_alpha = ddesc->ddpfPixelFormat.dwAlphaBitDepth > 0 ? true : false;

    D3DX11GetImageInfoFromMemory( data, file_size, NULL, &Info, &hr );
    if( hr != S_OK ) {        //get image info error
        delete [ ] data;
        return;
    }

    has_alpha = FindAlpha( Info.Format );

    LoadInfo.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    LoadInfo.CpuAccessFlags = 0;
    LoadInfo.Depth = 1;
    LoadInfo.Filter = D3DX11_FILTER_NONE;
    LoadInfo.FirstMipLevel = 0;
    LoadInfo.Format = Info.Format;
    LoadInfo.Height = height = Info.Height;
    LoadInfo.Width = width = Info.Width;
    LoadInfo.Usage = D3D11_USAGE_IMMUTABLE;
    LoadInfo.pSrcInfo = &Info;
    LoadInfo.MiscFlags = 0;
	LoadInfo.MipLevels = textureMgr->getGraphicsClient()->getConfig()->TextureMipMapCount;
    LoadInfo.MipFilter = D3DX11_FILTER_LINEAR;

    D3DX11CreateShaderResourceViewFromMemory( gd3dDevice, data, file_size, &LoadInfo, NULL, &SRV, &hr );
    bSRV = true;

    delete [ ] data;

    if( hr != S_OK )    Tex = NULL;            //SRV creation error
    else                SRV->GetResource( (ID3D11Resource**)&Tex );
#if _DEBUG
    if (SUCCEEDED(hr))
    {
        char buffer[200];
        buffer[199] = '\0';
        sprintf(buffer, "[Texture::ctor]%s", fname);
        if (SRV)
            SRV->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
        if (Tex)
            Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
    }
#endif
    bTex = true;
    Surf = NULL;
    RTV = NULL;
    bRTV = bColorKey = false;
    ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
    RefCounter = 1;
    return;
}

//==========================================================================
//==========================================================================

Texture::~Texture() {
//Release() is thread safe
    /*if( Tex )    Tex->Release();
    if( Surf )    Surf->Release();
    if( SRV )    SRV->Release();
    if( RTV )    RTV->Release();*/
    if( file_name )    delete [ ] file_name;
}

void Texture::GetSize( DWORD *w, DWORD *h ) {
    *w = width;
    *h = height;
}

void Texture::SetColorKey( DWORD ckey ) {
//color key can be used in blit for transparency
    ColorKey = D3DXCOLOR( ckey );
    bColorKey = true;
}

bool Texture::CreateGDIShadowSurface()
{
    if (!Tex)
        return false;
    if (m_gdiTex)
        return true;
    D3D11_TEXTURE2D_DESC desc;
    Tex->GetDesc(&desc);
    auto hr = gd3dDevice->CreateTexture2D(&desc, nullptr, &m_gdiTex);
    if (FAILED(hr))
        return false;
    hr = m_gdiTex.QueryInterface(&Surf);
    if (FAILED(hr))
        return false;
    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
    ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
    srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Format = desc.Format;
    srvDesc.Texture2D.MipLevels = 1;
    hr = gd3dDevice->CreateShaderResourceView(m_gdiTex, &srvDesc, &m_gdiTexSRV);
    if (FAILED(hr))
        return false;
    D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
    ZeroMemory(&rtvDesc, sizeof(D3D11_RENDER_TARGET_VIEW_DESC));
    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
    rtvDesc.Format = desc.Format;
    rtvDesc.Texture2D.MipSlice = 0;
    hr = gd3dDevice->CreateRenderTargetView(m_gdiTex, &rtvDesc, &m_gdiTexRTV);
    if (FAILED(hr))
        return false;
    return true;
}

HDC Texture::GetDC() {
/*    returns GDI DC for surface    (thread-unsafe)
    works only for B8G8R8A8 textures
    slow
*/
    if (!CreateGDIShadowSurface())
        return nullptr;
    //opCode = 1 - copy to result and set alpha to 1
    m_textureMgr->Execute2DOperation(GetSRV(), &m_gdiTexRTV, width, height, 1);
    HDC hdc;
    HRESULT hr;
    hr = Surf->GetDC( FALSE, &hdc );
    if( hr == S_OK )
        return hdc;
    else {
        oapiWriteLog( "GetDC() failed");
        return NULL;
    }
}

void Texture::ReleaseDC() {
//releases GDI DC;
    Surf->ReleaseDC( NULL );
    m_gdiUsed = true;
    //opCode = 2 - copy to result where alpha is 0
    m_textureMgr->Execute2DOperation(&m_gdiTexSRV, GetRTV(), width, height, 2);
}

ID3D11ShaderResourceView **Texture::CreateSRV() {
//creates SRV for surface
    D3D11_SHADER_RESOURCE_VIEW_DESC SDesc;

    ZeroMemory( &SDesc, sizeof( SDesc ));
    SDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    SDesc.Texture2D.MipLevels = 1;
    SDesc.Texture2D.MostDetailedMip = 0;
    SDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

    HR( gd3dDevice->CreateShaderResourceView( Tex, &SDesc, &SRV ) );
    /*auto hr = gd3dDevice->CreateShaderResourceView( Tex, &SDesc, &SRV );
    D3D11_TEXTURE2D_DESC tDesc;
    if (FAILED(hr))
    {
        Tex->GetDesc(&tDesc);
    }*/
    bSRV = true;
    return &SRV;
}

ID3D11RenderTargetView **Texture::CreateRTV() {
    if( bTex )    return NULL;    //RTV cannot be created for a texture

    D3D11_RENDER_TARGET_VIEW_DESC RDesc;

    ZeroMemory( &RDesc, sizeof( RDesc ) );
    RDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    RDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
    RDesc.Texture2D.MipSlice = 0;

    HR( gd3dDevice->CreateRenderTargetView( Tex, &RDesc, &RTV ) );
    bRTV = true;
    return &RTV;
}

bool Texture::FindAlpha( DXGI_FORMAT format ) {
    switch( format ) {
    case DXGI_FORMAT_BC2_UNORM:
    case DXGI_FORMAT_BC3_UNORM:
    case DXGI_FORMAT_BC4_UNORM:
    case DXGI_FORMAT_BC5_UNORM:
    case DXGI_FORMAT_R8G8B8A8_UNORM:
    case DXGI_FORMAT_B5G5R5A1_UNORM:
    case DXGI_FORMAT_B8G8R8A8_UNORM:
    case DXGI_FORMAT_B8G8R8X8_UNORM:
        return true;
    }
    return false;
}
